package com.wocao.framework.security;

import com.wocao.framework.security.filter.JwtAuthTokenFilter;
import com.wocao.framework.security.handler.FailAccessDeniedHandler;
import com.wocao.framework.security.handler.FailAuthenticationHandler;
import com.wocao.framework.security.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailServiceImpl userDetailsService;

    @Autowired
    private JwtAuthTokenFilter jwtAuthTokenFilter;

    @Autowired
    private FailAuthenticationHandler failAuthenticationHandler;

    @Autowired
    private FailAccessDeniedHandler failAccessDeniedHandler;

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）
     * hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问
     * hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问
     * hasRole             |   如果有参数，参数表示角色，则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 我们可以实现相关接口,自定义的对认证/鉴权失败做处理,保持我们接口的返回信息一致.
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(failAuthenticationHandler).and()
                // 鉴权失败处理类
                .exceptionHandling().accessDeniedHandler(failAccessDeniedHandler).and()
                // 基于token，所以不需要session,这里设置STATELESS(无状态)是在请求是不生成session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //配置权限
                .authorizeRequests()
                //对于登录注册等接口,允许匿名访问[匿名访问一定不需要传token,否则会认为权限不足,如果非要使用则用permitAll]
                .antMatchers("/sys/login", "/sys/sendRegisterEmail", "/sys/checkEmailCode", "/sys/register", "/doc.html", "/").permitAll()
                // swagger相关
                .antMatchers("/swagger-ui.html", "/swagger**/**", "/webjars/**","/v2/api-docs").permitAll()
                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                //有其中一个角色就可以访问,直接指定
                .antMatchers("/order").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
                //存在ROLE_ADMIN的角色才可以访问,ROLE是自己加上去的,实际上传入的角色是ADMIN
                .antMatchers("/user", "/role", "/menu").hasAnyRole("ADMIN")
                //除上面外的所有请求全部需要登录认证
                .anyRequest().authenticated().and()
                //因为不使用session禁用csrf
                .csrf().disable();
        //登出功能
        httpSecurity.logout().logoutUrl("/logout");
        //添加JWTfilter, 在每次http请求前进行拦截
        httpSecurity.addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //调用DetailsService完成用户身份验证设置密码加密方式
        auth.userDetailsService(userDetailsService).passwordEncoder(getBCryptPasswordEncoder());
    }

    // 在通过数据库验证登录的方式中不需要配置此种密码加密方式, 因为已经在JWT配置中指定
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
